/*
 ***************************************************************************************
 *  Copyright (C) 2006 EsperTech, Inc. All rights reserved.                            *
 *  http://www.espertech.com/esper                                                     *
 *  http://www.espertech.com                                                           *
 *  ---------------------------------------------------------------------------------- *
 *  The software in this package is published under the terms of the GPL license       *
 *  a copy of which has been included with this distribution in the license.txt file.  *
 ***************************************************************************************
 */
package com.espertech.esper.common.internal.epl.join.exec.base;

import com.espertech.esper.common.client.EventBean;
import com.espertech.esper.common.internal.epl.expression.core.ExprEvaluatorContext;
import com.espertech.esper.common.internal.epl.join.strategy.ExecNode;
import com.espertech.esper.common.internal.util.IndentWriter;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

/**
 * Execution node that performs a nested iteration over all child nodes.
 * <p>
 * Each child node under this node typically represents a table lookup. The implementation
 * 'hops' from the first child to the next recursively for each row returned by a child.
 * <p>
 * It passes a 'prototype' row (prefillPath) to each new child which contains the current partial event set.
 */
public class NestedIterationExecNode extends ExecNode {
    private final LinkedList<ExecNode> childNodes;
    private final int[] nestedStreams;
    private int nestingOrderLength;

    /**
     * Ctor.
     *
     * @param nestedStreams - array of integers defining order of streams in nested join.
     */
    public NestedIterationExecNode(int[] nestedStreams) {
        this.nestedStreams = nestedStreams;
        this.childNodes = new LinkedList<ExecNode>();
    }

    /**
     * Add a child node.
     *
     * @param childNode to add
     */
    public void addChildNode(ExecNode childNode) {
        childNodes.add(childNode);
    }

    public void process(EventBean lookupEvent, EventBean[] prefillPath, Collection<EventBean[]> result, ExprEvaluatorContext exprEvaluatorContext) {
        nestingOrderLength = childNodes.size();
        recursiveNestedJoin(lookupEvent, 0, prefillPath, result, exprEvaluatorContext);
    }

    /**
     * Recursive method to run through all child nodes and, for each result set tuple returned
     * by a child node, execute the inner child of the child node until there are no inner child nodes.
     *
     * @param lookupEvent          - current event to use for lookup by child node
     * @param nestingOrderIndex    - index within the child nodes indicating what nesting level we are at
     * @param currentPath          - prototype result row to use by child nodes for generating result rows
     * @param result               - result tuple rows to be populated
     * @param exprEvaluatorContext context for expression evalauation
     */
    protected void recursiveNestedJoin(EventBean lookupEvent, int nestingOrderIndex, EventBean[] currentPath, Collection<EventBean[]> result, ExprEvaluatorContext exprEvaluatorContext) {
        List<EventBean[]> nestedResult = new LinkedList<EventBean[]>();
        ExecNode nestedExecNode = childNodes.get(nestingOrderIndex);
        nestedExecNode.process(lookupEvent, currentPath, nestedResult, exprEvaluatorContext);
        boolean isLastStream = nestingOrderIndex == nestingOrderLength - 1;

        // This is not the last nesting level so no result rows are added. Invoke next nesting level for
        // each event found.
        if (!isLastStream) {
            for (EventBean[] row : nestedResult) {
                EventBean lookup = row[nestedStreams[nestingOrderIndex]];
                recursiveNestedJoin(lookup, nestingOrderIndex + 1, row, result, exprEvaluatorContext);
            }
            return;
        }

        // Loop to add result rows
        for (EventBean[] row : nestedResult) {
            result.add(row);
        }
    }

    public void print(IndentWriter writer) {
        writer.println("NestedIterationExecNode");
        writer.incrIndent();

        for (ExecNode child : childNodes) {
            child.print(writer);
        }
        writer.decrIndent();
    }
}
